/* diskio.c - Modified for SDIO & SPI Flash support with dynamic device binding */
#include <rtthread.h>
#include "ff.h"
#include "diskio.h"
#include "w25qxx.h"

/* Drive definitions */
#define DEV_SDIO 0     /* SD Card via SDIO */
#define DEV_SPIFLASH 1 /* SPI Flash */

/* Device name mappings */
static struct
{
    const char *sdio_name;
    const char *flash_name;
} disk_devices = {0};

/*----------------------------------------------*/
/* SPI Flash Low Level Driver                   */
/*----------------------------------------------*/

/**
 * @brief Get SPI Flash device by name
 * @return w25qxx_device_t* or NULL if not found
 */
static w25qxx_device_t *get_spiflash_device(void)
{
    return disk_devices.flash_name ? w25qxx_find(disk_devices.flash_name) : NULL;
}

/**
 * @brief Initialize SPI Flash device
 * @return DSTATUS Operation status
 */
static DSTATUS spiflash_disk_initialize(void)
{
    w25qxx_device_t *dev = get_spiflash_device();
    if (!dev)
        return STA_NOINIT;

    int ret = w25qxx_init_device(dev);
    return (ret == W25QXX_OK) ? 0 : STA_NOINIT;
}

/**
 * @brief Get SPI Flash status
 * @return DSTATUS Device status
 */
static DSTATUS spiflash_disk_status(void)
{
    w25qxx_device_t *dev = get_spiflash_device();
    if (!dev)
        return STA_NOINIT;

    uint8_t status;
    int ret = w25qxx_read_status(dev, 1, &status);

    if (ret != W25QXX_OK)
        return STA_NOINIT;

    return (status & W25QXX_STATUS_BUSY_BIT) ? STA_NOTREADY : 0;
}

/**
 * @brief Read sectors from SPI Flash
 */
static DRESULT spiflash_disk_read(
    BYTE *buff,   /* Data buffer */
    LBA_t sector, /* Start sector */
    UINT count    /* Sector count */
)
{
    w25qxx_device_t *dev = get_spiflash_device();
    if (!dev)
        return RES_NOTRDY;

    uint32_t addr = sector << 12; /* 4096-byte sectors */
    uint32_t size = count << 12;

    int ret = w25qxx_fast_read(dev, addr, (uint8_t *)buff, size);
    return (ret == W25QXX_OK) ? RES_OK : RES_ERROR;
}

/**
 * @brief Write sectors to SPI Flash
 */
#if FF_FS_READONLY == 0
static DRESULT spiflash_disk_write(
    const BYTE *buff, /* Data buffer */
    LBA_t sector,     /* Start sector */
    UINT count        /* Sector count */
)
{
    w25qxx_device_t *dev = get_spiflash_device();
    if (!dev)
        return RES_NOTRDY;

    uint32_t addr = sector << 12;
    uint32_t size = count << 12;
    const uint8_t *src = buff;

    /* Erase each sector */
    for (UINT i = 0; i < count; i++)
    {
        int ret = w25qxx_erase_sector(dev, addr);
        if (ret != W25QXX_OK)
            return RES_ERROR;
        addr += 4096;
    }

    /* Program pages */
    addr = sector << 12;
    while (size > 0)
    {
        uint32_t chunk = (size > 256) ? 256 : size;
        int ret = w25qxx_page_program(dev, addr, src, chunk);
        if (ret != W25QXX_OK)
            return RES_ERROR;

        src += chunk;
        addr += chunk;
        size -= chunk;
    }

    return RES_OK;
}
#endif /* FF_FS_READONLY == 0 */

/**
 * @brief SPI Flash control function
 */
static DRESULT spiflash_disk_ioctl(
    BYTE cmd,  /* Control command */
    void *buff /* Parameter buffer */
)
{
    w25qxx_device_t *dev = get_spiflash_device();
    if (!dev)
        return RES_NOTRDY;

    switch (cmd)
    {
    case CTRL_SYNC:
        return w25qxx_wait_busy(dev, 1000) ? RES_ERROR : RES_OK;

    case GET_SECTOR_SIZE:
        *(DWORD *)buff = 4096; /* Fixed 4K sector size */
        return RES_OK;

    case GET_BLOCK_SIZE:
        *(DWORD *)buff = 1; /* 1 block per erase unit */
        return RES_OK;

    case GET_SECTOR_COUNT:
        *(LBA_t *)buff = dev->config.sector_count;
        return RES_OK;

    case CTRL_TRIM: /* Erase block */
        return w25qxx_erase_sector(dev, *(LBA_t *)buff << 12) ? RES_ERROR : RES_OK;

    default:
        return RES_PARERR;
    }
}

/*----------------------------------------------*/
/* SDIO Low Level Driver (Stubs)                */
/*----------------------------------------------*/

/* SDIO functions to be implemented later */
static DSTATUS sdio_disk_initialize(void) { return STA_NOINIT; }
static DSTATUS sdio_disk_status(void) { return STA_NOINIT; }
static DRESULT sdio_disk_read(BYTE *buff, LBA_t sector, UINT count)
{
    return RES_NOTRDY;
}
#if FF_FS_READONLY == 0
static DRESULT sdio_disk_write(const BYTE *buff, LBA_t sector, UINT count)
{
    return RES_NOTRDY;
}
#endif
static DRESULT sdio_disk_ioctl(BYTE cmd, void *buff) { return RES_NOTRDY; }

/*-----------------------------------------------------------------------*/
/* FatFs Disk I/O Interface                                              */
/*-----------------------------------------------------------------------*/

DSTATUS disk_initialize(BYTE pdrv)
{
    switch (pdrv)
    {
    case DEV_SDIO:
        return sdio_disk_initialize();
    case DEV_SPIFLASH:
        return spiflash_disk_initialize();
    default:
        return STA_NOINIT;
    }
}

DSTATUS disk_status(BYTE pdrv)
{
    switch (pdrv)
    {
    case DEV_SDIO:
        return sdio_disk_status();
    case DEV_SPIFLASH:
        return spiflash_disk_status();
    default:
        return STA_NOINIT;
    }
}

DRESULT disk_read(
    BYTE pdrv,    /* Physical drive */
    BYTE *buff,   /* Data buffer */
    LBA_t sector, /* Start sector */
    UINT count    /* Sector count */
)
{
    switch (pdrv)
    {
    case DEV_SDIO:
        return sdio_disk_read(buff, sector, count);
    case DEV_SPIFLASH:
        return spiflash_disk_read(buff, sector, count);
    default:
        return RES_PARERR;
    }
}

#if FF_FS_READONLY == 0
DRESULT disk_write(
    BYTE pdrv,        /* Physical drive */
    const BYTE *buff, /* Data to write */
    LBA_t sector,     /* Start sector */
    UINT count        /* Sector count */
)
{
    switch (pdrv)
    {
    case DEV_SDIO:
        return sdio_disk_write(buff, sector, count);
    case DEV_SPIFLASH:
        return spiflash_disk_write(buff, sector, count);
    default:
        return RES_PARERR;
    }
}
#endif /* FF_FS_READONLY == 0 */

DRESULT disk_ioctl(
    BYTE pdrv, /* Physical drive */
    BYTE cmd,  /* Control command */
    void *buff /* Parameter buffer */
)
{
    switch (pdrv)
    {
    case DEV_SDIO:
        return sdio_disk_ioctl(cmd, buff);
    case DEV_SPIFLASH:
        return spiflash_disk_ioctl(cmd, buff);
    default:
        return RES_PARERR;
    }
}

/*----------------------------------------------*/
/* Device Registration API                      */
/*----------------------------------------------*/

/**
 * @brief Register SDIO device for FatFs
 * @param dev_name SDIO device name
 */
void disk_register_sdio_device(const char *dev_name)
{
    disk_devices.sdio_name = dev_name;
}

/**
 * @brief Register SPI Flash device for FatFs
 * @param dev_name Flash device name
 */
void disk_register_flash_device(const char *dev_name)
{
    disk_devices.flash_name = dev_name;
}

DWORD get_fattime(void)
{
#if defined(USER_USING_RTC)
    RTC_TimeTypeDef sTime;
    RTC_DateTypeDef sDate;
    HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
    HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);

    return ((sDate.Year) << 25) | ((sDate.Month) << 21) | ((sDate.Date) << 16) | ((sTime.Hours) << 11) | ((sTime.Minutes) << 5) | ((sTime.Seconds) >> 1);
#else
    /* 如果没有RTC，返回一个固定时间 */
    return ((2023 - 1980) << 25) | (1 << 21) | (1 << 16) | (0 << 11) | (0 << 5) | (0 >> 1);
#endif
}